//The MIT License

//Copyright (c) 2009 Lluis Sanchez, Nick Darnell

//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace Polymorph
{
    /// <summary>
    /// The <see cref="CodeFieldReference"/> class is used to represent a reference to
    /// a field.
    /// </summary>
    public class CodeFieldReference : CodeValueReference
    {
        private CodeExpression m_target;
        private FieldInfo m_field;

        /// <summary>
        /// Initializes a new instance of <see cref="CodeFieldReference"/> referencing the field
        /// on the specified target expression.  This field must not be static.
        /// </summary>
        /// <param name="target">The target expression to access the field on.</param>
        /// <param name="field">The field to access.</param>
        public CodeFieldReference(CodeExpression target, FieldInfo field)
        {
            m_target = target;
            m_field = field;

            if (m_field.IsStatic)
            {
                throw new InvalidOperationException("Static member '" + m_field.Name +
                    "' cannot be accessed with an instance reference.");
            }
        }

        /// <summary>
        /// Initializes anew instance of <see cref="CodeFieldReference"/> referencing the static
        /// field.
        /// </summary>
        /// <param name="field">The field to access.</param>
        public CodeFieldReference(FieldInfo field)
        {
            m_field = field;

            if (!m_field.IsStatic)
            {
                throw new InvalidOperationException(
                    "An object reference is required for the non-static field '" +
                    m_field.Name + "'.");
            }
        }

        /// <summary>
        /// Generates the IL needed to reference the field.
        /// </summary>
        /// <param name="gen">The generator to use to emit the IL.</param>
        public override void Generate(ILGenerator gen)
        {
            if (m_field.IsStatic)
            {
                gen.Emit(OpCodes.Ldsfld, m_field);
            }
            else
            {
                m_target.Generate(gen);
                gen.Emit(OpCodes.Ldfld, m_field);
            }
        }

        /// <summary>
        /// Generates the IL necessary to assign the given expression to the field.
        /// </summary>
        /// <param name="gen">The generator to use to emit the IL.</param>
        /// <param name="value">The expression to assign to the field.</param>
        public override void GenerateSet(ILGenerator gen, CodeExpression value)
        {
            if (m_field.IsStatic)
            {
                value.Generate(gen);
                CodeGenerationHelper.GenerateSafeConversion(gen, m_field.FieldType, value.ResultType);
                gen.Emit(OpCodes.Stsfld, m_field);
            }
            else
            {
                m_target.Generate(gen);
                value.Generate(gen);
                CodeGenerationHelper.GenerateSafeConversion(gen, m_field.FieldType, value.ResultType);
                gen.Emit(OpCodes.Stfld, m_field);
            }
        }

        /// <summary>
        /// Prints the human readable representation of the field reference to the provided 
        /// <see cref="CodeWriter"/>.
        /// </summary>
        /// <param name="cw">The <see cref="CodeWriter"/> to write to.</param>
        public override void PrintCode(CodeWriter cw)
        {
            if (!m_field.IsStatic)
                m_target.PrintCode(cw);
            else
                cw.Write(m_field.DeclaringType.Name);

            cw.Write(".");
            cw.Write(m_field.Name);
        }

        /// <summary>
        /// Gets the type of the field reference.
        /// </summary>
        public override Type ResultType
        {
            get { return m_field.FieldType; }
        }
    }
}